﻿#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stddef.h>
//结构是⼀些值的集合,这些值称为成员变量
//结构的每个成员可以是不同类型的变量

//struct Stu
//{
//  //成员
//	char name[20];//名字 
//	int age;//年龄 
//	char sex[5];//性别 
//	char id[20];//学号 
//};//此地创建的变量为全局结构体变量; //分号不能丢 
//
////匿名的结构体类型,只能使⽤⼀次
////struct
////{
////	int a;
////	char b;
////	float c;
////}x;
//
//结构体变量定义和初始化
//int main()
//{
//	//内部创建的为局部变量
//	//按照结构体成员的顺序初始化 
//	struct Stu s = { "张三", 20, "男", "20230818001" };
//	printf("name: %s\n", s.name);
//	printf("age : %d\n", s.age);
//	printf("sex : %s\n", s.sex);
//	printf("id : %s\n", s.id);
//
//	//按照指定的顺序初始化 
//	struct Stu s2 = { .age = 18, .name = "lisi", .id = "20230818002", .sex =
//   "⼥" };
//	printf("name: %s\n", s2.name);
//	printf("age : %d\n", s2.age);
//	printf("sex : %s\n", s2.sex);
//	printf("id : %s\n", s2.id);
//	return 0;
//}

//数据结构:数据在内存中的存储结构
//线形，顺序表，链表 

//结构体的自引用
//结构体里面包含同类型的结构体是不行的
//结构体里面包含同类型的结构体指针是行的
//struct Node {
//	int data;
//	struct Node* next;
//};
//
////typedef,对结构体指针重命名为linklist
////第一种写法
//typedef struct Node
//{
//	int data;
//	struct Node* next;
//}*linklist;
////第二种
//struct Node
//{
//	int data;
//	struct Node* next;
//};
//typedef struct Node* linklist;

//结构体内存对齐：计算结构体的⼤⼩
//对齐规则
//1.结构体的第1个成员对⻬到和结构体变量起始位置偏移量为0的地址处
//2.从第2个成员变量开始,都要对⻬到某个对⻬数的整数倍的地址处
//对⻬数=编译器默认的⼀个对⻬数与该成员变量⼤⼩的较⼩值,vs默认对齐数为8
//Linux中gcc没有默认对⻬数,对⻬数就是成员⾃⾝的⼤⼩
//3.结构体总⼤⼩为最⼤对⻬数(结构体中每个成员变量都有⼀个对⻬数,所有对⻬数中最⼤的)的整数倍。
//4.如果嵌套了结构体的情况,嵌套的结构体成员对⻬到⾃⼰的成员中最⼤对⻬数的整数倍处
//结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体中成员的对⻬数)的整数倍

//宏：offsetof:返回一个结构体中成员的偏移量
//包含头文件stddef.h
//offsetof(结构体类型，成员);
//struct s1
//{
//	double a;
//	char b;
//	int c;
//};
//struct s2 {
//	char a;
//	struct s1 b;
//	double c;
//};
//int main() {
//	struct s1 s;
//	printf("%d\n", sizeof(struct s1));
//	printf("%d\n", offsetof(struct s1, a));//0
//	printf("%d\n", offsetof(struct s1, b));//4
//	printf("%d\n", offsetof(struct s1, c));//8
//	//嵌套
//	printf("\n%d\n", sizeof(struct s2));
//	printf("%d\n", offsetof(struct s2, a));
//	printf("%d\n", offsetof(struct s2, b));
//	printf("%d\n", offsetof(struct s2, c));
//	return 0;
//}
//内存对齐意义：拿空间来换取时间的做法

//根据内存对齐合理规划设计结构体成员
//让占⽤空间⼩的成员尽量集中在⼀起

//修改默认对齐数
//#pragma这个预处理指令,可以改变编译器的默认对⻬数
//#pragma pack(4)//把默认对齐数改成4
//struct s1
//{
//	double a;
//	char b;
//	int c;
//};
//#pragma pack()//默认对齐数恢复8
//int main() {
//	struct s1 s;
//	printf("%d\n", sizeof(struct s1));
//	printf("%d\n", offsetof(struct s1, a));//0
//	printf("%d\n", offsetof(struct s1, b));//4
//	printf("%d\n", offsetof(struct s1, c));//8
//	return 0;
//}

//#pragma once:头文件中使用：防止头文件被多次引用

//结构体传参
//struct S
//{
//	int data[1000];
//	int num;
//};
////结构体传参 
//void print1(struct S s)
//{
//	int i = 0;
//	for (i = 0; i < 3; i++) {
//		printf("%d ", s.data[i]);
//	}
//	printf("%d\n", s.num);
//}
////结构体地址传参 
//void print2(const struct S* ps)
//{
//	int i = 0;
//	for (i = 0; i < 3; i++) {
//		printf("%d ", ps->data[i]);
//	}
//	printf("%d\n", ps->num);
//}
//int main()
//{
//	struct S s = { {1,2,3,4}, 1000 };
//	print1(s); //传结构体 (传值调用)
//	print2(&s); //传地址 (传址调用)优选
////函数传参的时候，参数是需要压栈，会有时间和空间上的系统开销。
////如果传递⼀个结构体对象的时候，结构体过⼤
////参数压栈的的系统开销⽐较⼤，所以会导致性能的下降
//	return 0;
//}

//位段:靠结构体实现，单位（bit），用来节省空间的
//声明与结构体类似而有两个不同
//位段的成员必须是 int,unsigned int或signed int
//在C99中位段成员的类型也可以选择其他整型家族类型,⽐如：char
//位段的成员名后边有⼀冒号和⼀个数字
//冒号后数字大小不能超过前面类型大小
struct A
{
	//4byte -32bit
	int _a : 2;//表示a只需要2个bit
	int _b : 5;
	int _c : 10;
	//还剩余15bit,不够d，再开辟4byte
	//15+32bit
	//d是存放的15+15，还是32bit中的30bit是不确定的
	int _d : 30;
};
int main() {
	printf("%d\n", sizeof(struct A));
	return 0;
}
//1.位段的成员可以是 int,unsigned int,signed int或者是char等类型
//2.位段的空间上是按照需要以4个字节(int)或者1个字节(char)的⽅式来开辟的
//3.位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使⽤位段
//混合类型的可能性较低，不确定性

//1.int位段被当成有符号数还是⽆符号数是不确定的
//2.位段中最⼤位的数⽬不能确定.(16位机器最⼤16,32位机器最⼤32,写成27,在16位机器会出问题
//3.位段中的成员在内存中从左向右分配,还是从右向左分配,标准尚未定义。
//4.当⼀个结构包含两个位段,第⼆个位段成员⽐较⼤,⽆法容纳于第⼀个位段剩余的位时
//是舍弃剩余的位还是利⽤，这是不确定的

//位段的⼏个成员共有同⼀个字节，这样有些成员的起始位置并不是某个字节的起始位置，那么这些位
//置处是没有地址的。内存中每个字节分配⼀个地址，⼀个字节内部的bit位是没有地址的。
//所以不能对位段的成员使⽤& 操作符，这样就不能使⽤scanf直接给位段的成员输⼊值，只能是先输⼊
//放在⼀个变量中，然后赋值给位段的成员
struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};
int main()
{
	struct A sa = { 0 };
	scanf("%d", &sa._b);//这是错误的 

	//正确的⽰范 
	int b = 0;
	scanf("%d", &b);
	sa._b = b;
	return 0;
}

